#define DUP2(F, n) F(n) F(n+1)
#define DUP4(F, n) DUP2(F, n) DUP2(F, n + 2)
#define DUP8(F, n) DUP4(F, n) DUP4(F, n + 4)
#define DUP16(F, n) DUP8(F, n) DUP8(F, n + 8)
#define DUP32(F, n) DUP16(F, n) DUP16(F, n + 16)
#define C_NEXT_INSN code_ptr += 2; break
#define C_QUADRANT(n) \
    case n+(0 << 2): case n+(1 << 2): case n+(2 << 2): case n+(3 << 2): \
    case n+(4 << 2): case n+(5 << 2): case n+(6 << 2): case n+(7 << 2): \
    case n+(8 << 2): case n+(9 << 2): case n+(10 << 2): case n+(11 << 2): \
    case n+(12 << 2): case n+(13 << 2): case n+(14 << 2): case n+(15 << 2): \
    case n+(16 << 2): case n+(17 << 2): case n+(18 << 2): case n+(19 << 2): \
    case n+(20 << 2): case n+(21 << 2): case n+(22 << 2): case n+(23 << 2): \
    case n+(24 << 2): case n+(25 << 2): case n+(26 << 2): case n+(27 << 2): \
    case n+(28 << 2): case n+(29 << 2): case n+(30 << 2): case n+(31 << 2): 


C_QUADRANT(0)
    funct3 = (insn >> 13) & 7;
    rd = ((insn >> 2) & 7) | 8;
    switch(funct3) {
    case 0: /* c.addi4spn */
        imm = get_field1(insn, 11, 4, 5) |
            get_field1(insn, 7, 6, 9) |
            get_field1(insn, 6, 2, 2) |
            get_field1(insn, 5, 3, 3);
        if (imm == 0)
            goto illegal_insn;
        s->reg[rd] = (int64_t)(s->reg[2] + imm);
        break;
    case 2: /* c.lw */
        {
            uint32_t rval;
            imm = get_field1(insn, 10, 3, 5) |
                get_field1(insn, 6, 2, 2) |
                get_field1(insn, 5, 6, 6);
            rs1 = ((insn >> 7) & 7) | 8;
            addr = (int64_t)(s->reg[rs1] + imm);
            if (riscv64_read_u32(s, &rval, addr))
                goto mmu_exception;
            s->reg[rd] = (int32_t)rval;
        }
        break;
    case 3: /* c.ld */
        {
            uint64_t rval;
            imm = get_field1(insn, 10, 3, 5) |
                get_field1(insn, 5, 6, 7);
            rs1 = ((insn >> 7) & 7) | 8;
            addr = (int64_t)(s->reg[rs1] + imm);
            if (riscv64_read_u64(s, &rval, addr))
                goto mmu_exception;
            s->reg[rd] = (int64_t)rval;
        }
        break;
    case 6: /* c.sw */
        imm = get_field1(insn, 10, 3, 5) |
            get_field1(insn, 6, 2, 2) |
            get_field1(insn, 5, 6, 6);
        rs1 = ((insn >> 7) & 7) | 8;
        addr = (int64_t)(s->reg[rs1] + imm);
        val = s->reg[rd];
        if (riscv64_write_u32(s, addr, val))
            goto mmu_exception;
        break;
    case 7: /* c.sd */
        imm = get_field1(insn, 10, 3, 5) |
            get_field1(insn, 5, 6, 7);
        rs1 = ((insn >> 7) & 7) | 8;
        addr = (int64_t)(s->reg[rs1] + imm);
        val = s->reg[rd];
        if (riscv64_write_u64(s, addr, val))
            goto mmu_exception;
        break;
    default:
        goto illegal_insn;
    }
    C_NEXT_INSN;
C_QUADRANT(1)
    funct3 = (insn >> 13) & 7;
    switch(funct3) {
    case 0: /* c.addi/c.nop */
        if (rd != 0) {
            imm = sext(get_field1(insn, 12, 5, 5) |
                        get_field1(insn, 2, 0, 4), 6);
            s->reg[rd] = (int64_t)(s->reg[rd] + imm);
        }
        break;
    case 1: /* c.addiw */
        if (rd != 0) {
            imm = sext(get_field1(insn, 12, 5, 5) |
                        get_field1(insn, 2, 0, 4), 6);
            s->reg[rd] = (int32_t)(s->reg[rd] + imm);
        }
        break;
    case 2: /* c.li */
        if (rd != 0) {
            imm = sext(get_field1(insn, 12, 5, 5) |
                        get_field1(insn, 2, 0, 4), 6);
            s->reg[rd] = imm;
        }
        break;
    case 3:
        if (rd == 2) {
            /* c.addi16sp */
            imm = sext(get_field1(insn, 12, 9, 9) |
                        get_field1(insn, 6, 4, 4) |
                        get_field1(insn, 5, 6, 6) |
                        get_field1(insn, 3, 7, 8) |
                        get_field1(insn, 2, 5, 5), 10);
            if (imm == 0)
                goto illegal_insn;
            s->reg[2] = (int64_t)(s->reg[2] + imm);
        } else if (rd != 0) {
            /* c.lui */
            imm = sext(get_field1(insn, 12, 17, 17) |
                        get_field1(insn, 2, 12, 16), 18);
            s->reg[rd] = imm;
        }
        break;
    case 4: 
        funct3 = (insn >> 10) & 3;
        rd = ((insn >> 7) & 7) | 8;
        switch(funct3) {
        case 0: /* c.srli */ 
        case 1: /* c.srai */ 
            imm = get_field1(insn, 12, 5, 5) |
                get_field1(insn, 2, 0, 4);
            if (funct3 == 0)
                s->reg[rd] = (int64_t)((uint64_t)s->reg[rd] >> imm);
            else
                s->reg[rd] = (int64_t)s->reg[rd] >> imm;
            
            break;
        case 2: /* c.andi */
            imm = sext(get_field1(insn, 12, 5, 5) |
                        get_field1(insn, 2, 0, 4), 6);
            s->reg[rd] &= imm;
            break;
        case 3: 
            rs2 = ((insn >> 2) & 7) | 8;
            funct3 = ((insn >> 5) & 3) | ((insn >> (12 - 2)) & 4);
            switch(funct3) {
            case 0: /* c.sub */
                s->reg[rd] = (int64_t)(s->reg[rd] - s->reg[rs2]);
                break;
            case 1: /* c.xor */
                s->reg[rd] = s->reg[rd] ^ s->reg[rs2];
                break;
            case 2: /* c.or */
                s->reg[rd] = s->reg[rd] | s->reg[rs2];
                break;
            case 3: /* c.and */
                s->reg[rd] = s->reg[rd] & s->reg[rs2];
                break;
            case 4: /* c.subw */
                s->reg[rd] = (int32_t)(s->reg[rd] - s->reg[rs2]);
                break;
            case 5: /* c.addw */
                s->reg[rd] = (int32_t)(s->reg[rd] + s->reg[rs2]);
                break;
            default:
                goto illegal_insn;
            }
            break;
        }
        break;
    case 5: /* c.j */
        imm = sext(get_field1(insn, 12, 11, 11) | 
                    get_field1(insn, 11, 4, 4) |
                    get_field1(insn, 9, 8, 9) |
                    get_field1(insn, 8, 10, 10) |
                    get_field1(insn, 7, 6, 6) |
                    get_field1(insn, 6, 7, 7) |
                    get_field1(insn, 3, 1, 3) |
                    get_field1(insn, 2, 5, 5), 12);
        s->pc = (int64_t)(GET_PC() + imm);
        JUMP_INSN;
    case 6: /* c.beqz */
        rs1 = ((insn >> 7) & 7) | 8;
        imm = sext(get_field1(insn, 12, 8, 8) | 
                    get_field1(insn, 10, 3, 4) |
                    get_field1(insn, 5, 6, 7) |
                    get_field1(insn, 3, 1, 2) |
                    get_field1(insn, 2, 5, 5), 9);
        if (s->reg[rs1] == 0) {
            s->pc = (int64_t)(GET_PC() + imm);
            JUMP_INSN;
        }
        break;
    case 7: /* c.bnez */
        rs1 = ((insn >> 7) & 7) | 8;
        imm = sext(get_field1(insn, 12, 8, 8) | 
                    get_field1(insn, 10, 3, 4) |
                    get_field1(insn, 5, 6, 7) |
                    get_field1(insn, 3, 1, 2) |
                    get_field1(insn, 2, 5, 5), 9);
        if (s->reg[rs1] != 0) {
            s->pc = (int64_t)(GET_PC() + imm);
            JUMP_INSN;
        }
        break;
    default:
        goto illegal_insn;
    }
    C_NEXT_INSN;
C_QUADRANT(2)
    funct3 = (insn >> 13) & 7;
    rs2 = (insn >> 2) & 0x1f;
    switch(funct3) {
    case 0: /* c.slli */
        imm = get_field1(insn, 12, 5, 5) | rs2;
        if (rd != 0)
            s->reg[rd] = (int64_t)(s->reg[rd] << imm);
        break;
    case 2: /* c.lwsp */
        {
            uint32_t rval;
            imm = get_field1(insn, 12, 5, 5) |
                (rs2 & (7 << 2)) |
                get_field1(insn, 2, 6, 7);
            addr = (int64_t)(s->reg[2] + imm);
            if (riscv64_read_u32(s, &rval, addr))
                goto mmu_exception;
            if (rd != 0)
                s->reg[rd] = (int32_t)rval;
        }
        break;
    case 3: /* c.ldsp */
        {
            uint64_t rval;
            imm = get_field1(insn, 12, 5, 5) |
                (rs2 & (3 << 3)) |
                get_field1(insn, 2, 6, 8);
            addr = (int64_t)(s->reg[2] + imm);
            if (riscv64_read_u64(s, &rval, addr))
                goto mmu_exception;
            if (rd != 0)
                s->reg[rd] = (int64_t)rval;
        }
        break;
    case 4:
        if (((insn >> 12) & 1) == 0) {
            if (rs2 == 0) {
                /* c.jr */
                if (rd == 0)
                    goto illegal_insn;
                s->pc = s->reg[rd] & ~1;
                JUMP_INSN;
            } else {
                /* c.mv */
                if (rd != 0)
                    s->reg[rd] = s->reg[rs2];
            }
        } else {
            if (rs2 == 0) {
                if (rd == 0) {
                    /* c.ebreak */
                    s->pending_exception = CAUSE_BREAKPOINT;
                    goto exception;
                } else {
                    /* c.jalr */
                    val = GET_PC() + 2;
                    s->pc = s->reg[rd] & ~1;
                    s->reg[1] = val;
                    JUMP_INSN;
                }
            } else {
                if (rd != 0) {
                    s->reg[rd] = (int64_t)(s->reg[rd] + s->reg[rs2]);
                }
            }
        }
        break;
    case 6: /* c.swsp */
        imm = get_field1(insn, 9, 2, 5) |
            get_field1(insn, 7, 6, 7);
        addr = (int64_t)(s->reg[2] + imm);
        if (riscv64_write_u32(s, addr, s->reg[rs2]))
            goto mmu_exception;
        break;
    case 7: /* c.sdsp */
        imm = get_field1(insn, 10, 3, 5) |
            get_field1(insn, 7, 6, 8);
        addr = (int64_t)(s->reg[2] + imm);
        if (riscv64_write_u64(s, addr, s->reg[rs2]))
            goto mmu_exception;
        break;
    default:
        goto illegal_insn;
    }
    C_NEXT_INSN;
